/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.editpart;

import java.util.Iterator;
import java.util.StringTokenizer;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.gef.EditDomain;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.tools.CellEditorLocator;
import org.eclipse.gef.tools.DirectEditManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.window.Window;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.uml2.common.edit.command.ChangeCommand;
import org.eclipse.uml2.uml.Component;
import org.eclipse.uml2.uml.DataType;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ElementImport;
import org.eclipse.uml2.uml.Enumeration;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.PrimitiveType;
import org.eclipse.uml2.uml.Profile;
import org.eclipse.uml2.uml.Reception;
import org.eclipse.uml2.uml.Signal;
import org.eclipse.uml2.uml.Stereotype;

import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.commands.EMFtoGEFWrapCommand;
import com.cea.papyrus.core.directedit.FigureControler;
import com.cea.papyrus.core.directedit.IDirectEdition;
import com.cea.papyrus.core.directedit.ILabelControler;
import com.cea.papyrus.core.directedit.LabelCellEditorLocator;
import com.cea.papyrus.core.directedit.LabelDirectEditManager;
import com.cea.papyrus.core.directedit.PropertyAccessor;
import com.cea.papyrus.core.directedit.SimpleCellEditorLocator;
import com.cea.papyrus.core.editor.ModelManager;
import com.cea.papyrus.core.figures.ElementFigure;
import com.cea.papyrus.core.figures.IconFigure;
import com.cea.papyrus.core.helper.ModelerHelper;
import com.cea.papyrus.core.listener.UmlElementDestroyListener;
import com.cea.papyrus.core.listener.UmlElementListener;
import com.cea.papyrus.core.preference.ProfileApplicationPreferenceConstants;
import com.cea.papyrus.core.ui.dialogs.ExtendedDirectEditionDialog;
import com.cea.papyrus.core.ui.properties.advanced.EObjectPropertySource;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Property;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;
import com.cea.papyrus.extensionpoints.editors.configuration.IDirectEditorConfiguration;
import com.cea.papyrus.extensionpoints.editors.utils.DirectEditorsUtil;
import com.cea.papyrus.extensionpoints.editors.utils.IDirectEditorsIds;

/**
 * Edit part in charge of the management of a UML element. Most edit part in Papyrus should inherit from this class.
 */
public class GraphNodeWithSemanticEditPart 	extends GraphNodeEditPart implements IUMLElementEditPart {
	
	/** listener for changes in UML model */
	protected UmlElementListener _adapter ;
	
	/** listener specific to UML destroy events*/
	protected UmlElementDestroyListener destroyListener;
	
	/** Manager for editing class name */
	protected DirectEditManager manager;

	/** direct edition mode (default, undefined, registered editor, etc.) */
	protected int directEditionMode = IDirectEdition.UNDEFINED_DIRECT_EDITOR;

	/** configuration from a registered edit dialog */
	private IDirectEditorConfiguration configuration;
	
	/**
	 * Creates a new GraphNodeWithSemanticEditPart instance.
	 * All element that can be display is a graphNode.
	 * @param modelManager manager in charge of models, graphic and domain
	 * @param graphNode parent of the graph node controlled by this edit part
	 */
	public GraphNodeWithSemanticEditPart(GraphNode graphNode, ModelManager modelManager){
		super(graphNode, modelManager);
		_adapter=new UmlElementListener(this);
		destroyListener=new UmlElementDestroyListener(this);
	}

	/**
	 * The model the is associated to this element is a GraphNode.
	 * @return the GraphNode
	 */
	@Override
	public GraphNode getGraphNode() {
		return (GraphNode)getModel();
	}

	/**
	 * Return the stereotype presentation (String) or "".
	 * 
	 * @return 
	 */
	protected String getPresentation() {

		String presentation = "";
		String default_presentation = ModelerHelper.TEXT_STEREOTYPE_PRESENTATION; 

		if ((getGraphNode() == null) || (getGraphNode().getSemanticModel() == null)) {
			return default_presentation;
		}

		if (getGraphNode().getSemanticModel().getPresentation() != null) {
			presentation = getGraphNode().getSemanticModel().getPresentation();
		} else {
			getGraphNode().getSemanticModel().setPresentation(default_presentation);
			presentation = default_presentation;
		}

		return presentation;
	}
	

	/**
	 * Returns the Figure of this, as a node type figure.
	 * The default figure is a elemt node : a red rectangle
	 * 
	 * @return  Figure as a NodeFigure.
	 */
	@Override
	protected ElementFigure getNodeFigure(){
		return (ElementFigure)createFigure() ;
	}


	/**
	 * return if the Edit part is visible.
	 * 
	 * @return 
	 */
	@Override
	public boolean isVisible() {
		return getGraphNode().isIsVisible();
	}

	/**
	 * 
	 * 
	 * @param b 
	 */
	@Override
	public void setVisible(boolean b) {
		getGraphNode().setIsVisible(b);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractEditPart#createEditPolicies()
	 */
	/**
	 * 
	 */
	protected void createEditPolicies() {
		super.createEditPolicies();
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.common.editpart.IUMLElementEditPart#getUmlElement()
	 */
	/**
	 * 
	 * 
	 * @return 
	 */
	public Element getUmlElement() {
		Uml1SemanticModelBridge umlBridge = (Uml1SemanticModelBridge) getGraphNode().getSemanticModel();
		if (umlBridge == null) {
			return null;
		}
		return umlBridge.getElement();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.IActionFilter#testAttribute()
	 */
	/**
	 * 
	 * 
	 * @param value 
	 * @param target 
	 * @param name 
	 * 
	 * @return 
	 */
	@Override
	public boolean testAttribute(Object target, String name, String value) {

		if (target instanceof GraphNodeWithSemanticEditPart) {
			Element targetUmlElement = ((GraphNodeWithSemanticEditPart) target).getUmlElement();

			return umlFilter.testAttribute(targetUmlElement, name, value);
		}

		return false;

	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.common.editpart.IUMLElementEditPart#getUmlElement()
	 */
	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	public boolean isElementImport() {
		if (getGraphNode().getSemanticModel()==null) {
			return false;
		}
		return  (((Uml1SemanticModelBridge)getGraphNode().getSemanticModel()).getElement() instanceof ElementImport);

	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.common.editpart.GraphNodeWithSemanticEditPart#getPropertySource()
	 */
	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	protected IPropertySource getPropertySource() {
		if( propertySource == null ) {
			propertySource = new EObjectPropertySource( this.getUmlElement());
		}
		return propertySource;	
	}
	/*
	 * (non-Javadoc)
	 * @see com.cea.papyrus.core.common.editpart.IUMLElementEditPart#getElementImport()
	 */
	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	public ElementImport getElementImport(){
		if (getGraphNode().getSemanticModel()==null) {
			return null;
		}
		if (((Uml1SemanticModelBridge)getGraphNode().getSemanticModel()).getElement() instanceof ElementImport){
			return (ElementImport)((Uml1SemanticModelBridge)getGraphNode().getSemanticModel()).getElement();
		}
		return null;	
	}


	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractEditPart#refreshVisuals()
	 */
	/**
	 * 
	 */
	@Override
	protected void refreshVisuals() {

		Dimension size = getGraphNode().getSize();
		Point p = getGraphNode().getPosition();
		Rectangle r = new Rectangle(p ,size);
		String name="This GraphNode is not associated to an Uml Element";

		ElementFigure f = (ElementFigure)getFigure();
		f.setName(name);

		((GraphicalEditPart) getParent()).setLayoutConstraint(
				this,
				f,
				r);

	}




	/**
	 * {@inheritDoc}
	 */
	@Override
	public void activate()
	{
		if (isActive()) {
			return;
		}
		super.activate();

		// start listening for changes in the model
		if (isElementImport()){
			((Notifier)getElementImport()).eAdapters().add(_adapter);
		}
		if (getUmlElement()!=null){
			((Notifier)getUmlElement()).eAdapters().add(_adapter);
			((Notifier)getUmlElement()).eAdapters().add(destroyListener);
			if (!getUmlElement().getAppliedStereotypes().isEmpty()) {
				Iterator<Stereotype> it=getUmlElement().getAppliedStereotypes().iterator();
				while(it.hasNext()){
					((Notifier)it.next()).eAdapters().add(_adapter);
				}
			}
		}
		if (getGraphNode().getSemanticModel()!=null){
			((Notifier) getGraphNode().getSemanticModel()).eAdapters().add(_adapter);}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#deactivate()
	 */
	/**
	 * 
	 */
	@Override
	public void deactivate()
	{
		if (!isActive()) {
			return;
		}

		// stop listening for changes in the model
		if (isElementImport()){
			((Notifier)getElementImport()).eAdapters().remove(_adapter);
		}
		if (getUmlElement()!=null) {
			((Notifier)getUmlElement()).eAdapters().remove(_adapter);
			((Notifier)getUmlElement()).eAdapters().remove(destroyListener);
			if (!getUmlElement().getAppliedStereotypes().isEmpty()) {
				Iterator<Stereotype> it=getUmlElement().getAppliedStereotypes().iterator();
				while(it.hasNext()){
					((Notifier)it.next()).eAdapters().remove(_adapter);
				}
			}
		}
		if (getGraphNode().getSemanticModel()!=null){
			((Notifier) getGraphNode().getSemanticModel()).eAdapters().remove(_adapter);}
		//if this is a icon figure we have to dispose associated image
		

		super.deactivate();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.notify.Adapter#getTarget()
	 */
	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	public Notifier getTarget() {
		return target;
	}

	
	
	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.notify.Adapter#setTarget(org.eclipse.emf.common.notify.Notifier)
	 */
	/**
	 * 
	 * 
	 * @param newTarget 
	 */
	@Override
	public void setTarget(Notifier newTarget) {
		target = newTarget;
	}


	/* (non-Javadoc)
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	/**
	 * 
	 * 
	 * @param key 
	 * 
	 * @return 
	 */
	@Override
	public Object getAdapter(Class key) {
		if (IPropertySource.class == key) {
			return getPropertySource();
		}

		return super.getAdapter( key );
	}




	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#createFigure()
	 */
	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	protected IFigure createFigure() {

		/** Stereotype presentation */		
		String stereotypePresentation = getPresentation();

		if(stereotypePresentation.equals(ModelerHelper.IMAGE_STEREOTYPE_PRESENTATION)) {

			org.eclipse.swt.graphics.Image img = PapyrusPlugin.getShapeElement(getUmlElement());

			if (img != null) {			
				return new IconFigure(img, getGraphNode().hashCode());
			}
		}		
		// else
		return null;		
	}
	
	/**
	 * Returns the Name Label of this edit part.
	 * @return Label the label figure from the graphical representation
	 */
	protected Label getNameLabel() {
		IFigure fig = getFigure();

		if (fig instanceof IconFigure) {
			return (Label) fig;
		} else if (fig instanceof ElementFigure) {
			return ((ElementFigure) fig).getNameLabel();
		} else {
			Debug.error(this, "GraphNodeWithSemanticEditPart::getNameLabel() : Unknown Figure type.", channel);
			return null;
		}
	}





	/**
	 * 
	 * 
	 * @return a string containing the stereotypes to display horizontally
	 */
	public String horizontalStereotypesToDisplay() {
		return stereotypesToDisplay(" ");
	}

	/**
	 * 
	 * 
	 * @return a string containing the stereotypes to display vertically
	 */
	public String verticalStereotypesToDisplay() {
		return stereotypesToDisplay("\n");
	}

	/**
	 * 
	 * 
	 * @param separator 
	 * 
	 * @return a string containing the stereotypes to display vertically
	 */
	public String stereotypesToDisplay(String separator) {
		EList displayStereotypesList = getGraphNode().getProperty();
		Iterator<Property> iter = displayStereotypesList.iterator();
		String stereotypesToDisplay="";
		
		// AL Changes Feb. 07 - Beg
		// Style Handling for STEREOTYPE_NAME_APPEARANCE from ProfileApplicationPreferencePage
		// Stereotype displayed according to UML standard (first letter forced to lower case) - default -
		// or kept as entered by user (user controlled)
		
		//Get the preference  from PreferenceStore
		IPreferenceStore store = PapyrusPlugin.getDefault().getPreferenceStore();		
		String sNameAppearance = store.getString(ProfileApplicationPreferenceConstants.P_STEREOTYPE_NAME_APPEARANCE);

		// AL Changes Feb. 07 - End
		//TODO: remove this and replace by figurelabel
		// Particular cases
		if (getUmlElement() instanceof Signal) {
			stereotypesToDisplay = "signal,"+separator;
		}else if (getUmlElement() instanceof Enumeration) {
			stereotypesToDisplay = "enumeration,"+separator;			
		} else if (getUmlElement() instanceof DataType && !(getUmlElement() instanceof PrimitiveType)) {
			stereotypesToDisplay = "dataType,"+separator;		
		} else if (getUmlElement() instanceof Interface) {
			stereotypesToDisplay = "interface,"+separator;
		} else if (getUmlElement() instanceof Reception) {
			stereotypesToDisplay = "signal,"+separator;
		} else if (getUmlElement() instanceof Model) {
			stereotypesToDisplay = "model,"+separator;
		} else if (getUmlElement() instanceof Profile) {
			stereotypesToDisplay = "profile,"+separator;
		} else if (getUmlElement() instanceof Stereotype) {
			stereotypesToDisplay = "stereotype,"+separator;
		}else if (getUmlElement() instanceof Component) {
			stereotypesToDisplay = "component,"+separator;
		}


		while(iter.hasNext()){
			Property prop = iter.next();
			// Bug correction: prop.getKey().equals(XXY) throws a NPE if getKey returns null 
			if(ModelerHelper.PROPERTY_STEREOTYPE_DISPLAY.equals(prop.getKey())){
				String qualifiedName = prop.getValue();

				// property value contains qualifiedName ==> extract name from it
				StringTokenizer strToken = new StringTokenizer(qualifiedName, "::");
				String name="";
				while(strToken.hasMoreTokens()) {
					name = strToken.nextToken();
				}
				// AL Changes Feb. 07 - Beg
				// Handling STEREOTYPE_NAME_APPEARANCE preference (from ProfileApplicationPreferencePage)
				// Previously lowercase forced onto first letter (standard UML)
				// stereotypesToDisplay = stereotypesToDisplay+name.substring(0, 1).toLowerCase()+name.substring(1, name.length())+","+separator;
				
				if (sNameAppearance.equals(ProfileApplicationPreferenceConstants.P_STEREOTYPE_NAME_DISPLAY_USER_CONTROLLED)) {
					stereotypesToDisplay = stereotypesToDisplay+name+","+separator;	
				} else if ( sNameAppearance.equals(ProfileApplicationPreferenceConstants.P_STEREOTYPE_NAME_DISPLAY_UML_CONFORM)) {
					stereotypesToDisplay = stereotypesToDisplay+name.substring(0, 1).toLowerCase()+name.substring(1, name.length())+","+separator;
				} else { // should not happen since radio button are used to set choice 
					stereotypesToDisplay = stereotypesToDisplay+name.substring(0, 1).toLowerCase()+name.substring(1, name.length())+","+separator;
				}				
				// AL Changes Feb. 07 - End
			}
		}
		
		if (!(stereotypesToDisplay.equals(""))) {
			stereotypesToDisplay = stereotypesToDisplay.substring(0, stereotypesToDisplay.length()-2);
		}

		return stereotypesToDisplay;
	}
	
	///////////////////////////////////////////////////////////////////////////
	// DIRECT EDIT ZONE
	///////////////////////////////////////////////////////////////////////////
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void performRequest(Request request){
		if (RequestConstants.REQ_DIRECT_EDIT.equals(request.getType())) {
			performDirectEdit(request);
		} else super.performRequest(request);
	}
	
	/**
	 * Init the label direct edit manager.
	 * This method should be called when a Direct Edit request is performed.
	 */
	protected void initLabelEditManager() {
		if(manager==null) {
			// locator for the cell editor (on the name label)
			final Label label = getNameLabel();
			CellEditorLocator locator;
			if(label !=null) {
				locator = new LabelCellEditorLocator(label);
			} else {
				locator = new SimpleCellEditorLocator(getFigure());
			}

			// defining how properties are modified in the model
			PropertyAccessor accessor = new PropertyAccessor() {
				public String getPropertyName() {
					return "Name";
				}

				public String getValue() {
					if(isElementImport()){
						return getElementImport().getAlias();
					} else if(getUmlElement() instanceof NamedElement) {
						return ((NamedElement)getUmlElement()).getName();
					}
					return "";
					
				}
				public void setValue(String value) {
					if(isElementImport()){
						getElementImport().setAlias(value);
					} else if(getUmlElement() instanceof NamedElement) {
						((NamedElement)getUmlElement()).setName(value);
					} 
				}
			};
			// defines the controller: link between model and view
			ILabelControler labelControler = new FigureControler(getFigure(), accessor);

			// creates the editing manager.
			manager = new LabelDirectEditManager(this, locator, labelControler);
		}
	}

	/**
	 * The direct edit mechanism requires the edit part to handle the direct edit
	 * request.
	 * @param req the request that called the direct edit.
	 */
	public void performDirectEdit(Request req) {
		if(IDirectEdition.UNDEFINED_DIRECT_EDITOR == directEditionMode) {
			directEditionMode = getDirectEditionType();
		}
		switch(directEditionMode) {
		case IDirectEdition.NO_DIRECT_EDITION:
			// no direct edition mode => does nothing
			return;
		case IDirectEdition.EXTENDED_DIRECT_EDITOR:
			// opens a dialog using specific configuration
			configuration.preEditAction(getUmlElement());
			final ExtendedDirectEditionDialog dialog = new ExtendedDirectEditionDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
					getUmlElement(),
					configuration.getTextToEdit(getUmlElement()),
					configuration);
			if(Window.OK==dialog.open()) {
				// apply modifications
				final AdapterFactoryEditingDomain emfDomain = getModelManager().getDiResourceSet().getEditingDomain();
				EMFtoGEFWrapCommand command = new EMFtoGEFWrapCommand(emfDomain) {
					@Override
					public Command createEMFCommand() {
						return new ChangeCommand(emfDomain, new Runnable() {
							public void run() {configuration.postEditAction(getUmlElement(), dialog.getValue());};
						}, "Edit Label", "Edit the label of the selected element");
					}
				};
				EditDomain gefDomain = getRoot().getViewer().getEditDomain();
				gefDomain.getCommandStack().execute(command);
			}

			break;
		case IDirectEdition.DEFAULT_DIRECT_EDITOR:
			// initialize the direct edit manager 
			initLabelEditManager();
			manager.show();
			break;
		default: 
			break;
		}

	}

	/**
	 * Returns the style of direct edition. 
	 * <p>Note: This method should never be overridden. Override sub-methods instead.</p>
	 * @return the style of direct edition
	 */
	protected int getDirectEditionType() {
		if(checkExtendedEditor()) {
			initExtendedEditorConfiguration();
			return IDirectEdition.EXTENDED_DIRECT_EDITOR;
		}
		if(checkDefaultEdition()) {
			return IDirectEdition.DEFAULT_DIRECT_EDITOR;
		}
		
		// not a named element. no specific editor => do nothing
		return IDirectEdition.NO_DIRECT_EDITION;
	}
	
	/** 
	 * Checks if an extended editor is present.
	 * @return <code>true</code> if an extended editor is present.
	 */ 
	protected boolean checkExtendedEditor() {
		if(getUmlElement() != null) {
			return DirectEditorsUtil.hasSpecificEditorConfiguration(IDirectEditorsIds.UML_LANGUAGE, getUmlElement().eClass().getInstanceClassName());	
		}
		return false; 
	}
	
	/**
	 * Checks if a default direct edition is available
	 * @return <code>true</code> if a default direct edition is available
	 */
	protected boolean checkDefaultEdition() {
		return (getUmlElement() instanceof NamedElement);
	}
	
	/**
	 * Initializes the extended editor configuration
	 */
	protected void initExtendedEditorConfiguration() {
		if(configuration == null) {
			configuration = DirectEditorsUtil.findEditorConfiguration(IDirectEditorsIds.UML_LANGUAGE, getUmlElement().eClass().getInstanceClassName());
		}
	}
}
